#ifndef INIREADER_EXTENSION_HPP
#define INIREADER_EXTENSION_HPP

#include "INIReader_noWarning.hpp"
#include "logger.hpp"
#include "formatHelpers.hpp"
#include <string_view>
#include <type_traits>
#include <limits>
#include <iostream>

namespace ini
{

/* just to hold the functions in a header file */
namespace internal
{

template<typename T>
static void warning(
	const std::string& section, const std::string& item, const T& defaultValue
){
	using namespace hms;
	hms::log::warn(
		"Could not find or could not parse configuration value:\n{}\"{}\""
		"\nUsing default value \"{}\".",
		indent(),
		bold("[{}] {}", section, item),
		bold("{}", defaultValue)
	);
}

template<typename T>
static constexpr T valueIfNotFound(){
	if constexpr ( std::is_arithmetic_v<T> )
		return std::numeric_limits<T>::max();
	else {
		static_assert( std::is_same_v<T, std::string> );
		return "__NOT_FOUND__";
	}
}

/* The function is copied from the lib, with the return type and default_value
 * type being changed to int and the parameters being adjusted, as it is
 * no longer a member function.
 * The body is left unchanged, except for the first line in which valstr is set,
 * where a "reader." has been inserted. */
inline int readBool(
	const INIReader& reader, const std::string& section,
	const std::string& name, int default_value
){
    std::string valstr = reader.Get(section, name, "");
    /* Convert to lower case to make string comparisons case-insensitive */
    std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
    if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
        return true;
    else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
        return false;
    else
        return default_value;
}

template<typename T>
bool read(
	const INIReader& reader, const std::string& section,
	const std::string& item, T& outValue, bool warnIfNotFound
){
	using ReadType = std::conditional_t<std::is_same_v<T,bool>,int,T>;
	ReadType readValue;

	#define INI_READ_ARGS section, item, valueIfNotFound<ReadType>()

	if constexpr ( std::is_same_v<T,bool> )
		readValue = readBool(reader, INI_READ_ARGS);
	else if constexpr ( std::is_integral_v<T> )
		readValue = reader.GetInteger(INI_READ_ARGS);
	else if constexpr ( std::is_floating_point_v<T> )
		readValue = reader.GetReal(INI_READ_ARGS);
	else {
		static_assert( std::is_same_v<T, std::string> );
		readValue = reader.Get(INI_READ_ARGS);
	}

	#undef INI_READ_ARGS

	if ( readValue == valueIfNotFound<ReadType>() ){
		if ( warnIfNotFound )
			warning(section, item, outValue);
		return false;
	} else {
		outValue = static_cast<T>(readValue);
		return true;
	}
}

} // namespace internal


template<typename T>
bool read(
	const INIReader& reader,
	const std::string& section,
	const std::string& item,
	T& value,
	bool warnIfNotFound = true
){
	return internal::read<T>( reader, section, item, value, warnIfNotFound );
}

} // namespace ini

#endif